﻿using System;
using System.IO;
using System.IO.Ports;
using System.Threading;

namespace nsWeldController
{
    /// <summary>
    /// 串口收发类
    /// </summary>
    public class SerialProtocol
    {
        public SerialProtocol()
        {
            
        }

        public SerialProtocol(string portName, int baudRate)
        {
            OpenSerialCom(portName, baudRate);
        }

        private CLog mLog = CWeldController.mLog;

        /// <summary>
        /// 用于监听调用的函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void ComDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            int bufLength = mSerialPort.BytesToRead;
            string strPrint = string.Empty;
            if (bufLength <= 6)
            {
                byte[] buf = new byte[bufLength];
                mSerialPort.Read(buf, 0, bufLength);
                strPrint = BytesToHexString(buf);
            }
            else
            {
                ET_GPES_RECVMSG stRecv = new ET_GPES_RECVMSG();
                EA_GP_ucRecvMsg(ref stRecv, 100);
                strPrint = BytesToHexString(stRecv.recvMsg);
            }

            mLog.LogMessage("接收到串口数据：" + strPrint + "\n");
        }

        public string BytesToHexString(byte[] bytes)
        {
            string strPrint = string.Empty;
            if (bytes == null)
            {
                return strPrint;
            }
            foreach (byte bt in bytes)
            {
                strPrint += (bt.ToString("X2") + " ");
            }
            return strPrint;
        }

        #region 串口收发函数
        public SerialPort mSerialPort = new SerialPort();

        public const byte ERR_PROTCOL = 0x10;
        public const byte ERR_EXECUTE = 0x20;
        public const byte ERR_RESULT = 0x30;
        public const byte RET_USERCANCEL = 0x40;
        public const byte RET_ABANDON = 0x50;
        public const byte RET_SUCCESS = 0x00;

        public const byte EM_STX = 0x02;
        public const byte EM_ETX = 0x03;
        public const byte EM_ACK = 0x06;
        public const byte EM_NAK = 0x15;
        public const byte EM_ETB = 0x17;

        /// <summary>
        /// 返回串口的打开状态
        /// </summary>
        /// <returns></returns>
        public bool IsOpen()
        {
            return mSerialPort.IsOpen;
        }

        /// <summary>
        /// 打开一个串口
        /// </summary>
        /// <param name="portName"></param>
        /// <param name="baudRate"></param>
        public void OpenSerialCom(string portName, int baudRate)
        {
            if (!mSerialPort.IsOpen)
            {
                mSerialPort.PortName = portName;
                mSerialPort.BaudRate = baudRate;

                try
                {
                    mSerialPort.Open();
                    mSerialPort.ReadTimeout = 800;
                    mSerialPort.WriteTimeout = 800;
                    //mSerialPort.DataReceived += ComDataReceived;
                    mLog.LogMessage("串口打开\n");
                }
                catch (Exception ex)
                {
                    mLog.LogError(ex.Message + "\n");
                }
            }
        }

        public void CloseSerialCom()
        {
            if (mSerialPort.IsOpen)
            {
                try
                {
                    mSerialPort.Close();
                    mLog.LogMessage("串口关闭\n");
                }
                catch (Exception ex)
                {
                    mLog.LogError(ex.Message + "\n");
                }
            }
        }

        /// <summary>
        /// 发送并接收
        /// </summary>
        /// <param name="ptSend"></param>
        /// <param name="ptRecv"></param>
        /// <param name="iTimeout"></param>
        /// <returns></returns>
        public byte EA_GP_ucTransMsg(ET_GPES_SENDMSG stSend, ref ET_GPES_RECVMSG stRecv, int iTimeout = 800)
        {
            mSerialPort.DiscardInBuffer();
            mSerialPort.DiscardOutBuffer();
            byte ucRet = ERR_EXECUTE;
            ucRet = EA_GP_ucSendMsg(stSend, iTimeout);
            if (ucRet != RET_SUCCESS)
            {
                return ucRet;
            }

            //Thread.Sleep(2);
            ucRet = ERR_EXECUTE;
            ucRet = EA_GP_ucRecvMsg(ref stRecv, iTimeout);
            if (ucRet != RET_SUCCESS)
            {
                return ucRet;
            }
            //mLog.LogDebug("查询到串口返回数据：" + BytesToHexString(stRecv.recvMsg) + "\n");
            return RET_SUCCESS;
        }

        //public byte EA_GP_ucSendMsg(ref ET_GPES_SENDMSG ptSend, int uiTimeOut, ref byte[] pucData, int puiLen);

        /// <summary>
        /// 串口发送
        /// </summary>
        /// <param name="ptSend"></param>
        /// <param name="iTimeOut"></param>
        /// <returns></returns>
        public byte EA_GP_ucSendMsg(ET_GPES_SENDMSG stSend, int iTimeOut = 800)
        {
            byte[] aucMsg = new byte[260];
            byte[] ucBuf = stSend.sendMsg;
            byte ucFrameNo = 1;
            byte ucEnd = EM_ETB;
            int uiSendLen = 0;
            int uiRet;
            int uiTolLen = stSend.sendMsg.Length;
            mSerialPort.ReadTimeout = iTimeOut;
            mSerialPort.WriteTimeout = iTimeOut;

            do
            {
                aucMsg[0] = EM_STX;
                if (uiTolLen > 250)
                {
                    uiSendLen = 250;
                }
                else
                {
                    uiSendLen = uiTolLen;
                }
                aucMsg[1] = (byte)(uiSendLen + 5);
                aucMsg[2] = stSend.CLA;
                aucMsg[3] = ucFrameNo;
                aucMsg[4] = stSend.INS;
                if (uiSendLen != 0)
                {
                    Buffer.BlockCopy(ucBuf, 0, aucMsg, 5, uiSendLen);
                }

                if (uiTolLen > 250)
                {
                    aucMsg[uiSendLen + 5] = ucEnd;
                }
                else
                {
                    aucMsg[uiSendLen + 5] = EM_ETX;
                }

                aucMsg[uiSendLen + 6] = EI_ucGetLrc(aucMsg, 3, uiSendLen + 3);
                try
                {
                    mSerialPort.Write(aucMsg, 0, uiSendLen + 7);
                }
                catch (Exception ex)
                {
                    mLog.LogError(ex.Message + "\n");
                    return ERR_EXECUTE;
                }

                uiTolLen -= uiSendLen;
                Buffer.BlockCopy(ucBuf, uiSendLen, ucBuf, 0, uiTolLen);

                // 帧号满时
                if (ucFrameNo == 0xFF)
                {
                    ucFrameNo = 1;
                    ucEnd++;
                }
                else
                {
                    ucFrameNo++;
                }

                // 接收ACK
                try
                {
                    uiRet = mSerialPort.Read(aucMsg, 0, 1);
                    if (uiRet != 1 || aucMsg[0] != EM_ACK)  //接收出错
                    {
                        mLog.LogError("接收ack错误:" + aucMsg[0].ToString("X2") + "\n");
                        return ERR_PROTCOL;
                    }
                }
                catch (Exception ex)
                {
                    mLog.LogDebug(ex.Message + "\n");
                    return ERR_EXECUTE;
                }
            } while (uiTolLen > 0);

            return RET_SUCCESS;
        }

        /// <summary>
        /// 串口接收
        /// </summary>
        /// <param name="ptRecv"></param>
        /// <param name="iTimeOut"></param>
        /// <returns></returns>
        public byte EA_GP_ucRecvMsg(ref ET_GPES_RECVMSG stRecv, int iTimeOut = 800)
        {
            byte[] aucMsg = new byte[260];
            byte ucFrameNo = 1;
            byte ucOldFrameNo = 0;
            int uiRecLen;
            int uiPos = 0;
            int uiRet;
            mSerialPort.ReadTimeout = iTimeOut;
            mSerialPort.WriteTimeout = iTimeOut;

            try
            {
                Stream recvStream = new MemoryStream(); // 用于读取数据
	            do
	            {
	                uiRet = mSerialPort.Read(aucMsg, 0, 1);
	
	                if (uiRet != 1 || aucMsg[0] != EM_STX)      // 接收STX
	                {
	                    return ERR_PROTCOL;
	                }
	
	                uiRet = mSerialPort.Read(aucMsg, 1, 1);     // Len
	                if (uiRet != 1)
	                {
	                    return ERR_PROTCOL;
	                }
	
	                uiRecLen = aucMsg[1];
	                // 帧最小长度为6字节，其中INS+FRAME_NO+ETX/ETB+LRC至少4字节
	                if (uiRecLen < 4)
	                {
	                    return ERR_PROTCOL;
	                }
                    
                    int bytesReaded = 0;
                    while (bytesReaded < uiRecLen)
                    {
                        bytesReaded += mSerialPort.Read(aucMsg, 2 + bytesReaded, uiRecLen - bytesReaded);
                        //mLog.LogDebug("bytesReaded={0}\n", bytesReaded);
                    }

                    string strPrint = "";
                    for (int i = 0; i < uiRecLen + 2; i++)
                    {
                        strPrint += (aucMsg[i].ToString("X2") + " ");
                    }
                    //mLog.LogDebug("接收到的完整数据:" + strPrint + "\n");
                    uiRet = bytesReaded;
	                if (uiRet != uiRecLen)
	                {
                        mLog.LogDebug("收到的数据长度异常,期望:" + uiRecLen.ToString() 
                            +" 实际："+ uiRet.ToString() + "\n");
                        return ERR_PROTCOL;
	                }
	                ucFrameNo = aucMsg[3];
	
	                if (ucFrameNo != (byte)(ucOldFrameNo + 1))
	                {
	                    // 帧号必须为递增1的关系
	                    return ERR_PROTCOL;
	                }
	                else
	                {
	                    ucOldFrameNo = ucFrameNo;
	                }
	
	                if (uiPos == 0)
	                {
	                    // 根据第一帧的RES设置返回值
	                    stRecv.RES = aucMsg[2];
	                }
	                else
	                {
	                    // 如果有多帧，则每帧的RES必须相同
	                    if (aucMsg[2] != stRecv.RES)
	                    {
	                        return ERR_PROTCOL;
	                    }
	                }
	
	                if (aucMsg[uiRecLen] < EM_ETB && aucMsg[uiRecLen] != EM_ETX)
	                {
	                    return ERR_PROTCOL;
	                }
	
	                // LRC校验
	                if (EI_ucGetLrc(aucMsg, 3, uiRecLen - 2) != aucMsg[uiRecLen + 1])
	                {
	                    return ERR_PROTCOL;
	                }

                    recvStream.Write(aucMsg, 4, uiRecLen - 4);
	                
	                uiPos += uiRecLen - 4;
	                aucMsg[0] = EM_ACK;

	                mSerialPort.Write(aucMsg, 0, 1);
	            } while (aucMsg[uiRecLen] == EM_ETB); // 还有后续帧
                stRecv.recvMsg = new byte[recvStream.Length];
                recvStream.Position = 0;
                recvStream.Read(stRecv.recvMsg, 0, stRecv.recvMsg.Length);
            }
            catch (System.Exception ex)
            {
                mLog.LogDebug(ex.Message + "\n");
                return ERR_EXECUTE;
            }

            stRecv.RetLen = uiPos;
            return RET_SUCCESS;
        }

        /// <summary>
        /// CRC校验
        /// </summary>
        /// <param name="pucBuf">要校验的byte数组</param>
        /// <param name="offset">pucBuf的偏移量，从0开始</param>
        /// <param name="uiLen">需要校验pucBuf的长度</param>
        /// <returns></returns>
        public byte EI_ucGetLrc(byte[] pucBuf, int offset, int uiLen)
        {
            byte ucLrc = 0;

            for (int i = 0; i < uiLen; i++)
            {
                ucLrc ^= pucBuf[i + offset];
            }
            return ucLrc;
        }
        #endregion
    }
}
